import * as xlsx from "node-xlsx";
import * as fs from "fs";
import * as path from "path";
import { type } from "os";
interface IConfig {
    "isPretty": boolean,
    "isArray": boolean,
    "client": boolean,
    "server": boolean,
    "input": string,

    "s_output": string,
    "s_ts_output": string,

    "c_output": string,
    "c_ts_output": string,
}
type Declare = {
    /** 模块注释 */
    m_comm: string,
    /** 模块下分类配置的注释 */
    i_comm: string,
    /** 模块名称 */
    m_class: string
    /** 子模块名称 */
    i_class: string
    /** 字段 */
    fields: Field[]
}
type Field = { name: string, comm: string }
console.log();

const config: IConfig = require("./config.json");
const Idx = { "server": 0, "client": 1 }
const TypeMap = { "bool": "boolean", "int": "number" }
/**
 * 开始执行
 */
function start() {
    let inputfiles: string[] = [];
    try { inputfiles = fs.readdirSync(config.input); } catch (e) { return console.error("directory input ", "非法输入路径", config.input) }
    // 服务端
    if (config.server) {
        // 没有目录就直接创建
        if (!fs.existsSync(config.s_output)) { fs.mkdirSync(config.s_output) }
        if (!fs.existsSync(config.s_ts_output)) { fs.mkdirSync(config.s_ts_output) }
        const declares: Declare[] = []
        // 导出
        inputfiles.forEach((filename) => parse(filename, config.s_output, "server", declares));
        // 导出ts类型声明文件
        wirte2ts(declares, config.s_ts_output, "server")
    }
    console.log()
    // 客户端
    if (config.client) {
        // 没有目录就直接创建
        if (!fs.existsSync(config.c_output)) { fs.mkdirSync(config.c_output) }
        if (!fs.existsSync(config.c_ts_output)) { fs.mkdirSync(config.c_ts_output) }
        const declares: Declare[] = []
        // 导出json
        inputfiles.forEach((filename) => parse(filename, config.c_output, "client", declares));
        // 导出ts类型声明文件
        wirte2ts(declares, config.c_ts_output, "client")
    }
}

/**
 * 创建ts类型声明文件
 * @param declares 类型包装类
 * @param outDir 输出目录
 * @param outType 输出类型
 */
function wirte2ts(declares: Declare[], outDir: string, outType: string) {
    console.log()
    let str = "/** 配置文件的类型定义 */\n"
    str += "declare namespace ConfigTypes {\n"
    for (const it of declares) {
        str += `\t/** ${it.m_comm}-${it.i_comm} */\n`
        str += `\ttype ${it.m_class}${it.i_class}Config = {\n`

        for (const field of it.fields) {
            str += `\t\t/** ${field.comm} */\n`
            str += `\t\t${field.name},\n`
        }
        str += "\t}\n"
    }
    str += "\n}"
    const fileName = `configuration.d.ts`
    // 输出路径
    const outpath = path.join(outDir, fileName)
    // 写入文件
    fs.writeFileSync(outpath, str);
    console.log(`------ >>> input to ${outType} --- excel >>> ${config.input} --- to ts >>> ${fileName}`);
}

/**
 * 解析单个表
 * @param filename 表格文件名
 * @param outDir 输出目录
 * @param outType 输出类型
 * @param declares ts类型声明包装类
 * @returns 
 */
function parse(filename: string, outDir: string, outType: string, declares: Declare[]) {

    if (filename[0] === "~") { return; }
    if (!filename.endsWith(".xlsx")) { return; }

    // 读取excel文件
    const buff = fs.readFileSync(path.join(config.input, filename));

    // 解析大模块的名称和注释
    const mNames = path.basename(filename, '.xlsx').split("-")
    if (mNames.length != 2 || !mNames[1] || !mNames[0]) {
        console.error(`丢！谁允许你不写注释？不写模块名称？不给导出。模块：${mNames[0]} 注释：${mNames[1]}`)
        return
    }

    const sheets = xlsx.parse(buff, { "raw": false });
    for (const sheet of sheets) {
        const lists: any = sheet.data;
        if (lists.length === 0) { continue; }

        // 类型声明对象创建添加
        const tsObj: Declare = { m_comm: "", i_comm: "", m_class: "", i_class: "", fields: [] }
        // 模块 名称
        tsObj.m_class = toUpStart(mNames[0])
        // 模块 类型注释
        tsObj.m_comm = mNames[1]

        const classNames = sheet.name.split("-")
        if (classNames.length != 2 || !classNames[1] || !classNames[0]) {
            console.error(`丢！谁允许你不写注释？不写子模块名称？不给导出。模块：${mNames[0]} 子模块：${classNames[0]} 注释：${classNames[1]}`)
            return
        }
        // 小模块名称
        tsObj.i_class = toUpStart(classNames[0])
        // 小模块注释
        tsObj.i_comm = classNames[1]

        /** 类型 数组 */
        const types: string[] = lists[Idx[outType]]
        /** 属性名 数组 */
        const fields: string[] = lists[2];
        /** 注释 数组 */
        const comments: string[] = lists[3];
        // 第二列开始才是真的属性类型
        for (let i = 2; i < fields.length; i++) {
            let type = types[i]
            if (type === "no") { continue }
            const field: Field = { name: "", comm: "" }
            let tsType = TypeMap[type]
            if (!tsType) {
                tsType = type.replace("int", "number").replace("bool", "boolean")
            }
            field.name = `${fields[i]}: ${tsType}`
            field.comm = comments[i]
            tsObj.fields.push(field)
        }
        declares.push(tsObj)

        /** 是否导出为数组 */
        const isArray = !!config.isArray;
        /** 服务端 导出的数据 */
        const obj: any = isArray ? { "data": [] } : {};

        // 前3行是注释等配置，后面的才是数据
        for (let i = 4; i < lists.length; i++) {
            // 是否导出
            if (!lists[i][0]) { continue }
            // 必须要有 唯一值key
            if (lists[i][1] === undefined) { continue; }

            const tmpInfo = createObj(fields, types, lists[i]);
            if (isArray) {
                obj.data.push(tmpInfo);
            } else {
                obj[lists[i][1]] = tmpInfo;
            }
        }
        // 格式化的间隔
        const spaceNum = !!config.isPretty ? 4 : 0;
        // json文件名
        const fileName = `${mNames[0]}_${classNames[0]}.json`
        // 输出路径
        const outpath = path.join(outDir, fileName)
        // 写入文件
        fs.writeFileSync(outpath, JSON.stringify(obj, null, spaceNum));
        // 输出提示
        console.log(`------ >>> input to ${outType} --- excel >>> ${config.input} --- to json >>> ${fileName}`);
    }

}


/**
 * 创建单个对象
 * @param fields 属性名数组
 * @param types 类型数组
 * @param datas 数据
 * @returns 
 */
function createObj(fields: string[], types: string[], datas: string[]) {
    const obj: any = {};
    for (let i = 2; i < fields.length; i++) {
        let type = types[i]
        // 该字段在服务端是否要导出
        if (type === "no") { continue }

        let value = datas[i]
        if (value === undefined) { value = ""; }

        let key = fields[i]
        if (type === "bool") {
            value = value.trim().toLowerCase();
            if (value === "0" || value == "" || value === "false") {
                obj[key] = false;
            } else if (value === "1" || value === "true") {
                obj[key] = true
            }
            console.error("配置类型值错误：type：", type, " value：", value)
            process.exit()

        } else if (type === "string") {
            obj[key] = value;
        } else if (type === "int") {
            obj[key] = Number(value) || 0;
        } else {
            // const tsType = type.replace("int", "number").replace("bool", "boolean")
            // console.log("tsType : ", tsType)
            obj[key] = JSON.parse(value)
        }
    }
    return obj;
}

/**
 * 把英文改为首字母大写
 * @param str 
 * @returns 
 */
function toUpStart(str: string) {
    return str.slice(0, 1).toUpperCase() + str.slice(1)
}


start()